import router from '@/router';
import store from '@/store';
import axios from 'axios';
import { Message, Loading } from 'element-ui';
import { checkLoginTimeout } from './auth';

const service = axios.create({
  // process 是 node 全局变量 global 下一个属性，所以可以直接用（跟浏览器全局变量 window 下的属性一样）
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000,
});

// 请求拦截器
service.interceptors.request.use(
  // config 就是请求配置对象
  config => {
    // [ token 注入
    const { token } = store.state.user;
    if (token) {
      if (checkLoginTimeout()) {
        return store.dispatch('user/logout').then(() => {
          router.push({ name: 'login' });
          return Promise.reject(new Error('token超时了'));
        });
      }
      config.headers.Authorization = `Bearer ${token}`;
    }
    // ] token 注入

    // [ 转圈圈
    // 从请求配置对象中 解构 加载容器
    const { loadingOptions } = config;

    // 如果有加载容器，就需要转圈圈
    if (loadingOptions) {
      // 发出请求前，开启转圈圈，并将转圈圈实例对象，绑定到请求配置对象上

      // Loading.service 方法的返回值 是 加载组件的 实例，在这个实例上有一个 close 方法，调用后，可以手动关闭加载中效果
      // 为什么我们要存储到 config.loadingInstance 请求配置对象上呢？
      // 因为我们要在请求完成后，关闭加载中效果
      // 为什么不讲 加载组件的 实例 放到一个都能访问到的作用域的变量中呢？
      // 因为请求在同一时刻 有可能发多个，上一次请求没完成前，可以继续发送下一个请求，所以不同的请求，要对应不同的 加载组件的实例， 不能互相影响（比如：第一个请求 将 第二个请求 的 加载效果 关闭）
      config.loadingInstance = Loading.service({
        // 选项的默认值 可以在这里先设置好
        lock: true,
        ...loadingOptions,
      });
      // 为什么要绑定在 请求配置对象上，而不是在外面搞一个变量？
      // 因为，请求有可能并发，多个请求之间的转圈圈实例应该是互不影响的
    }
    // ] 转圈圈

    // axios 最终 会以 该拦截器 成功回调 的 返回值 作为 请求配置
    // 一定要 return，否则 axios 将发不出去请求，因为没有请求配置对象，不知道往哪里发
    return config;
  },
  err => Promise.reject(err)
);
// 响应拦截器
service.interceptors.response.use(
  // 成功的回调
  res => {
    // 从 res 响应对象中 可以 通过 config 属性 获取到 对应的 请求配置对象
    // config 有可能为 undefined （当请求被手动取消时候）
    res.config?.loadingInstance?.close(); // 响应成功，关闭转圈

    const { success, message, data } = res.data;
    if (success) {
      return data;
    } else {
      Message.error(message);
      return Promise.reject(new Error(message)); // 必须返回一个失败的 Promise 实例，才能让后续的链式调用逻辑进入失败的回调
    }
  },
  // 失败的回调
  err => {
    // 从 err 错误对象中 可以 通过 config 属性 获取到 对应的 请求配置对象
    // config 有可能为 undefined （当请求被手动取消时候）
    err.config?.loadingInstance?.close(); // 响应失败，也要关闭转圈

    if (err.response?.data?.code === 10002) {
      Message.error(err.message);
      return store.dispatch('user/logout').then(() => {
        router.push({ name: 'login' });
      });
    } else {
      Message.error(err.message);
    }

    return Promise.reject(err); // 必须返回一个失败的 Promise 实例，才能让后续的链式调用逻辑进入失败的回调
  }
);

export default service;
